﻿/*
 * ===================================================
 * Author     : <ClueeZhuo>
 * Create Time: <2018/5/16 20:37:21>
 * Description: <This is a Description of This File.>
 * ===================================================
 */




using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Reflection;

namespace helloworld.IOC
{
    public class Cat
    {
        private ConcurrentDictionary<Type, Type> typeMapping = new ConcurrentDictionary<Type, Type>();

        public void Register(Type from, Type to)
        {
            typeMapping[from] = to;
        }

        public object GetService(Type serviceType)
        {
            Type type;

            if (!typeMapping.TryGetValue(serviceType, out type))
            {
                type = serviceType;
            }

            if (type.IsInterface || type.IsAbstract)
            {
                return null;
            }

            ConstructorInfo constructor = this.GetConstructor(type);

            if (constructor == null)
            {
                return null;
            }

            object[] arguments = constructor.GetParameters().Select(x => this.GetService(x.ParameterType)).ToArray();
            object service = constructor.Invoke(arguments);
            this.InitializeInjectedProperties(service);
            this.InvokeInjectedMethods(service);

            return service;
        }

        protected virtual ConstructorInfo GetConstructor(Type type)
        {
            ConstructorInfo[] constructorInfos = type.GetConstructors();

            return constructorInfos.FirstOrDefault(x => x.GetCustomAttribute<InjectionAttribute>() != null) ?? constructorInfos.FirstOrDefault();
        }

        protected virtual void InitializeInjectedProperties(object service)
        {
            PropertyInfo[] propertyInfos = service.GetType().GetProperties()
                .Where(x => x.CanWrite && x.GetCustomAttribute<InjectionAttribute>() != null)
                .ToArray();
            Array.ForEach(propertyInfos, x => x.SetValue(service, this.GetService(x.PropertyType)));
        }

        protected virtual void InvokeInjectedMethods(object service)
        {
            MethodInfo[] methodInfos = service.GetType().GetMethods()
                .Where(x => x.GetCustomAttribute<InjectionAttribute>() != null)
                .ToArray();

            Array.ForEach(methodInfos, x =>
            {
                object[] arguments = x.GetParameters().Select(y => this.GetService(y.ParameterType)).ToArray();
                x.Invoke(service, arguments);
            });
        }
    }

    public static class CatExtensions
    {
        public extern static T GetService<T>(this Cat cat);
    }

    /// <summary>
    /// 依赖注入特性
    /// </summary>
    [AttributeUsage(AttributeTargets.Constructor | AttributeTargets.Property | AttributeTargets.Method, AllowMultiple = false)]
    public class InjectionAttribute : Attribute
    {

    }
}
